package com.jichangxiu.framework.utils;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.jichangxiu.common.constant.Constants;
import com.jichangxiu.common.constant.RedisConstants;
import com.jichangxiu.common.context.JcxContext;
import com.jichangxiu.common.entity.bo.JcxContextEntity;
import com.jichangxiu.common.entity.bo.Payload;
import com.jichangxiu.common.utils.JwtUtils;
import com.jichangxiu.common.utils.RsaUtils;
import com.jichangxiu.common.utils.ServletUtils;
import com.jichangxiu.framework.entity.bo.SecurityToken;
import com.jichangxiu.framework.entity.bo.SecurityUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;

import javax.servlet.http.HttpServletRequest;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.concurrent.TimeUnit;

@Slf4j
public class SecurityUtils {

    private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L;

    public static String getUserId() {
        String userId = null;
        JcxContextEntity jcxContextEntity = JcxContext.getJcxContextEntity();
        if (ObjectUtil.isNotNull(jcxContextEntity)) {
            userId = jcxContextEntity.getUserId();
            if (ObjectUtil.isEmpty(userId)) {
                SecurityUser securityUser = JcxContext.getSecurityUser(SecurityUser.class);
                if (ObjectUtil.isNotNull(securityUser))
                    userId = securityUser.getUserId();
            }
        }
        if (ObjectUtil.isEmpty(userId)) {
            SecurityUser securityUser = getSecurityUser();
            if (ObjectUtil.isNotNull(securityUser))
                userId = securityUser.getUserId();
        }
        return userId;
    }

    public static String getUserName() {
        String userName = null;
        SecurityContext securityContext = SecurityContextHolder.getContext();
        if (ObjectUtil.isNotNull(securityContext)) {
            Authentication authentication = securityContext.getAuthentication();
            if (ObjectUtil.isNotEmpty(authentication)) userName = String.valueOf(authentication.getPrincipal());
            SecurityToken securityToken = (SecurityToken) securityContext;
            if (ObjectUtil.isNotEmpty(securityToken)) userName = String.valueOf(securityToken.getPrincipal());
        }
        if (ObjectUtil.isEmpty(userName)) {
            JcxContextEntity jcxContextEntity = JcxContext.getJcxContextEntity();
            if (ObjectUtil.isNotNull(jcxContextEntity)) userName = jcxContextEntity.getUserName();
        }
        if (ObjectUtil.isEmpty(userName)) {
            SecurityUser securityUser = JcxContext.getSecurityUser(SecurityUser.class);
            if (ObjectUtil.isNotNull(securityUser)) userName = securityUser.getUsername();
        }
        if (ObjectUtil.isEmpty(userName)) {
            SecurityUser securityUser = getSecurityUser();
            if (ObjectUtil.isNotNull(securityUser)) userName = securityUser.getUsername();
        }
        return userName;
    }

    public static String getTenantId() {
        String tenantId = null;
        SecurityContext securityContext = SecurityContextHolder.getContext();
        if (ObjectUtil.isNotNull(securityContext)) {
            SecurityToken securityToken = (SecurityToken) securityContext.getAuthentication();
            if (ObjectUtil.isNotEmpty(securityToken)) tenantId = String.valueOf(securityToken.getTenantId());
        }
        if (StrUtil.isEmpty(tenantId)) {
            JcxContextEntity jcxContextEntity = JcxContext.getJcxContextEntity();
            if (ObjectUtil.isNotEmpty(jcxContextEntity)) tenantId = jcxContextEntity.getTenantId();
        }
        if (StrUtil.isEmpty(tenantId)) {
            SecurityUser securityUser = JcxContext.getSecurityUser(SecurityUser.class);
            if (ObjectUtil.isNotEmpty(securityUser)) tenantId = securityUser.getTenantId();
        }
        if (ObjectUtil.isEmpty(tenantId)) {
            SecurityUser securityUser = getSecurityUser();
            if (ObjectUtil.isNotNull(securityUser)) tenantId = securityUser.getTenantId();
        }
        return tenantId;
    }

    public static String getTenantName() {
        String tenantName = null;
        JcxContextEntity jcxContextEntity = JcxContext.getJcxContextEntity();
        if (ObjectUtil.isNotEmpty(jcxContextEntity)) tenantName = jcxContextEntity.getTenantName();
        if (StrUtil.isEmpty(tenantName)) {
            SecurityUser securityUser = JcxContext.getSecurityUser(SecurityUser.class);
            if (ObjectUtil.isNotEmpty(securityUser)) tenantName = securityUser.getTenantName();
        }
        if (ObjectUtil.isEmpty(tenantName)) {
            SecurityUser securityUser = getSecurityUser();
            if (ObjectUtil.isNotNull(securityUser)) tenantName = securityUser.getTenantName();
        }
        return tenantName;
    }

    public static String getTenantSource() {
        String tenantSource = null;
        JcxContextEntity jcxContextEntity = JcxContext.getJcxContextEntity();
        if (ObjectUtil.isNotEmpty(jcxContextEntity)) tenantSource = jcxContextEntity.getTenantSource();
        if (StrUtil.isEmpty(tenantSource)) {
            SecurityUser securityUser = JcxContext.getSecurityUser(SecurityUser.class);
            if (ObjectUtil.isNotNull(securityUser)) tenantSource = securityUser.getTenantSource();
        }
        if (ObjectUtil.isEmpty(tenantSource)) {
            SecurityUser securityUser = getSecurityUser();
            if (ObjectUtil.isNotNull(securityUser)) tenantSource = securityUser.getTenantSource();
        }
        return tenantSource;
    }

    public static String getDeptId() {
        String deptId = null;
        JcxContextEntity jcxContextEntity = JcxContext.getJcxContextEntity();
        if (ObjectUtil.isNotEmpty(jcxContextEntity)) deptId = jcxContextEntity.getDeptId();
        if (StrUtil.isEmpty(deptId)) {
            SecurityUser securityUser = JcxContext.getSecurityUser(SecurityUser.class);
            if (ObjectUtil.isNotEmpty(securityUser)) deptId = securityUser.getDeptId();
        }
        if (ObjectUtil.isEmpty(deptId)) {
            SecurityUser securityUser = getSecurityUser();
            if (ObjectUtil.isNotNull(securityUser)) deptId = securityUser.getDeptId();
        }
        return deptId;
    }

    public static String getDeptName() {
        String deptName = null;
        JcxContextEntity jcxContextEntity = JcxContext.getJcxContextEntity();
        if (ObjectUtil.isNotEmpty(jcxContextEntity)) deptName = jcxContextEntity.getDeptName();
        if (StrUtil.isEmpty(deptName)) {
            SecurityUser securityUser = JcxContext.getSecurityUser(SecurityUser.class);
            if (ObjectUtil.isNotEmpty(securityUser)) deptName = securityUser.getDeptName();
        }
        if (ObjectUtil.isEmpty(deptName)) {
            SecurityUser securityUser = getSecurityUser();
            if (ObjectUtil.isNotNull(securityUser)) deptName = securityUser.getDeptName();
        }
        return deptName;
    }

    public static SecurityUser getUserInfo() {
        SecurityUser securityUser = JcxContext.getSecurityUser(SecurityUser.class);
        if (ObjectUtil.isEmpty(securityUser)) securityUser = getSecurityUser();
        return securityUser;
    }

    public static SecurityUser getUserInfo(HttpServletRequest httpServletRequest) {
        SecurityUser securityUser = JcxContext.getSecurityUser(SecurityUser.class);
        if (ObjectUtil.isEmpty(securityUser)) securityUser = getSecurityUser();
        if (ObjectUtil.isEmpty(securityUser)) securityUser = getSecurityUser(httpServletRequest);
        return securityUser;
    }

    public static Payload<SecurityUser> login(Object userInfo) {
        Payload<SecurityUser> payload = createPayload(userInfo);
        saveJwtToRedis(payload.getJti(), payload.getJwt());
        saveJtiToRedis(payload.getUserInfo().getTenantId(), payload.getUserInfo().getUserId(), payload.getJti());
        return payload;
    }

    private static Payload<SecurityUser> createPayload(Object userInfo) {
        try {
            String jwt = JwtUtils.createJwtExpireInSeconds(userInfo, RsaUtils.getPrivateKey(), Constants.JCX_PROPERTIES.getSecurityProperties().getAuthExpire());
            return JwtUtils.getUserInfoFromJwt(jwt, RsaUtils.getPublicKey(), SecurityUser.class);
        } catch (NoSuchAlgorithmException | InvalidKeySpecException ex) {
            throw new RuntimeException("【SecurityUtils】获取【RSA】私钥公钥异常", ex);
        } catch (Exception ex) {
            throw new RuntimeException("【SecurityUtils】创建【JWT】异常", ex);
        }
    }

    private static void saveJwtToRedis(String jti, String jwt) {
        RedisUtils.setCacheObject(RedisConstants.createJwtToRedisOfKey(Constants.JCX_PROPERTIES.getProjectName(), jti), jwt, Constants.JCX_PROPERTIES.getSecurityProperties().getAuthExpire(), TimeUnit.SECONDS);
    }

    private static void saveJtiToRedis(String tenantId, String userId, String jti) {
        RedisUtils.setCacheObject(RedisConstants.createJtiToRedisOfKey(Constants.JCX_PROPERTIES.getProjectName(), tenantId, userId), jti, Constants.JCX_PROPERTIES.getSecurityProperties().getAuthExpire(), TimeUnit.SECONDS);
    }

    public static void logout() {
        HttpServletRequest httpServletRequest = ServletUtils.getRequest();
        if (ObjectUtil.isNotEmpty(httpServletRequest)) {
            logout(httpServletRequest);
        }
    }

    public static void logout(HttpServletRequest httpServletRequest) {
        delJtiFromRedis(httpServletRequest);
        delJwt(httpServletRequest);
    }

    private static void delJtiFromRedis(HttpServletRequest request) {
        SecurityUser securityUser = getSecurityUser(request);
        if (ObjectUtil.isNotNull(securityUser)) delJtiFromRedis(securityUser.getTenantId(), securityUser.getUserId());
    }

    private static void delJtiFromRedis(String tenantId, String userId) {
        RedisUtils.deleteObject(RedisConstants.createJtiToRedisOfKey(Constants.JCX_PROPERTIES.getProjectName(), tenantId, userId));
    }

    private static void delJwt(HttpServletRequest httpServletRequest) {
        String jti = getJtiFromRequest(httpServletRequest);
        if (StrUtil.isNotEmpty(jti))
            RedisUtils.deleteObject(RedisConstants.createJwtToRedisOfKey(Constants.JCX_PROPERTIES.getProjectName(), jti));
    }

    public static String getJwtFromRedis() {
        String jwt = null;
        HttpServletRequest httpServletRequest = ServletUtils.getRequest();
        if (ObjectUtil.isNotEmpty(httpServletRequest)) {
            jwt = getJwtFromRedis(httpServletRequest);
        }
        return jwt;
    }

    public static String getJwtFromRedis(HttpServletRequest request) {
        String jwt = null;
        String jti = getJtiFromRequest(request);
        if (StrUtil.isNotEmpty(jti)) {
            String jwtToRedisOfKey = RedisConstants.createJwtToRedisOfKey(Constants.JCX_PROPERTIES.getProjectName(), jti);
            if (StrUtil.isNotEmpty(jwtToRedisOfKey))
                jwt = RedisUtils.getCacheObject(jwtToRedisOfKey);
        }
        return jwt;
    }

    public static String getJwtFromRedis(String jti) {
        String jwt = null;
        if (StrUtil.isNotEmpty(jti)) {
            String jwtToRedisOfKey = RedisConstants.createJwtToRedisOfKey(Constants.JCX_PROPERTIES.getProjectName(), jti);
            if (StrUtil.isNotEmpty(jwtToRedisOfKey))
                jwt = RedisUtils.getCacheObject(jwtToRedisOfKey);
        }
        return jwt;
    }

    public static String getJtiFromRedis(String tenantId, String userId) {
        String jti = null;
        if (StrUtil.isNotEmpty(tenantId) && StrUtil.isNotEmpty(userId)) {
            String jtiToRedisOfKey = RedisConstants.createJtiToRedisOfKey(Constants.JCX_PROPERTIES.getProjectName(), tenantId, userId);
            if (StrUtil.isNotEmpty(jtiToRedisOfKey))
                jti = RedisUtils.getCacheObject(jtiToRedisOfKey);
        }
        return jti;
    }

    public static String getJtiFromRequest() {
        String jti = null;
        HttpServletRequest httpServletRequest = ServletUtils.getRequest();
        if (ObjectUtil.isNotEmpty(httpServletRequest))
            jti = getJtiFromRequest(httpServletRequest);
        return jti;
    }

    public static String getJtiFromRequest(HttpServletRequest request) {
        String jti = null;
        if (ObjectUtil.isNotEmpty(request)) {
            // 从请求头获取
            jti = request.getHeader(Constants.JCX_PROPERTIES.getClientProperties().getAuthToClientKey());
            if (StrUtil.isEmpty(jti))
                // 如果从请求头没有获取到有效 jti 那么则从请求参数中获取
                jti = request.getParameter(Constants.JCX_PROPERTIES.getClientProperties().getAuthToClientKey());
        }
        // 如果添加了【Bearer 】需要去掉
        if (jti != null && StrUtil.isNotEmpty(jti) && Constants.JCX_PROPERTIES.getSecurityProperties().getAddJtiPrefix() && jti.startsWith(Constants.TOKEN_PREFIX))
            jti = jti.replace(Constants.TOKEN_PREFIX, "");
        return jti;
    }

    public static Payload<SecurityUser> getPayload() {
        Payload<SecurityUser> payload = null;
        String jti = getJtiFromRequest();
        if (StrUtil.isNotEmpty(jti))
            payload = getPayload(jti);
        return payload;

    }

    public static Payload<SecurityUser> getPayload(HttpServletRequest request) {
        Payload<SecurityUser> payload = null;
        if (ObjectUtil.isNotEmpty(request)) {
            String jti = getJtiFromRequest(request);
            if (StrUtil.isNotEmpty(jti))
                payload = getPayload(jti);
        }
        return payload;
    }

    public static Payload<SecurityUser> getPayload(String jti) {
        Payload<?> payload = null;
        if (StrUtil.isNotEmpty(jti)) {
            String jwt = getJwtFromRedis(jti);
            if (StrUtil.isNotEmpty(jwt)) {
                try {
                    payload = JwtUtils.getUserInfoFromJwt(jwt, RsaUtils.getPublicKey(), SecurityUser.class);
                    payload.setJwt(jwt);
                } catch (Exception ex) {
                    throw new RuntimeException("【SecurityUtils】获取用户身份信息异常", ex);
                }
            }
        }
        return (Payload<SecurityUser>) payload;
    }


    public static SecurityUser getSecurityUser() {
        SecurityUser securityUser = null;
        Payload<SecurityUser> payload = getPayload();
        if (ObjectUtil.isNotNull(payload))
            securityUser = payload.getUserInfo();
        return securityUser;
    }

    public static SecurityUser getSecurityUser(HttpServletRequest request) {
        SecurityUser securityUser = null;
        if (ObjectUtil.isNotNull(request)) {
            Payload<SecurityUser> payload = getPayload(request);
            if (ObjectUtil.isNotNull(payload)) {
                securityUser = payload.getUserInfo();
            }
        }
        return securityUser;
    }

    public static void verifyJwt() {
        String jti = getJtiFromRequest();
        if (StrUtil.isNotEmpty(jti))
            verifyJwt(jti);
    }

    public static void verifyJwt(HttpServletRequest request) {
        if (ObjectUtil.isNotNull(request)) {
            String jti = getJtiFromRequest(request);
            if (StrUtil.isNotEmpty(jti))
                verifyJwt(jti);
        }
    }

    public static void verifyJwt(String jti) {
        try {
            Payload<SecurityUser> payload = getPayload(jti);
            if (ObjectUtil.isNotNull(payload)) {
                long expireTime = payload.getExpiration().getTime();
                long currentTime = System.currentTimeMillis();
                if (expireTime - currentTime <= MILLIS_MINUTE_TEN) refreshJwt(jti);
            }
        } catch (Exception ex) {
            throw new RuntimeException("【SecurityUtils】校验用户【JWT】异常", ex);
        }
    }

    public static void refreshJwt() {
        String jti = getJtiFromRequest();
        if (StrUtil.isNotEmpty(jti))
            refreshJwt(jti);
    }

    public static void refreshJwt(HttpServletRequest request) {
        if (ObjectUtil.isNotNull(request)) {
            String jti = getJtiFromRequest(request);
            if (StrUtil.isNotEmpty(jti))
                refreshJwt(jti);
        }
    }

    public static void refreshJwt(String jti) {
        try {
            Payload<SecurityUser> payload = getPayload(jti);
            if (ObjectUtil.isNotNull(payload)) {
                SecurityUser userInfo = payload.getUserInfo();
                String jwt = JwtUtils.createJwtExpireInSeconds(userInfo, jti, RsaUtils.getPrivateKey(), Constants.JCX_PROPERTIES.getSecurityProperties().getAuthExpire());
                saveJwtToRedis(jti, jwt);
            }
        } catch (Exception ex) {
            throw new RuntimeException("【SecurityUtils】刷新用户【JWT】异常", ex);
        }
    }

    public static Boolean isPlatform() {
        boolean result = false;
        SecurityUser securityUser = getUserInfo();
        if (ObjectUtil.isNotNull(securityUser)) {
            result = securityUser.isPlatform();
            if (!result)
                result = Constants.TEN_SUPER_TENANT.equals(securityUser.getTenantCode());
        }
        return result;
    }

    public static Boolean isTenantAdmin() {
        boolean result = false;
        SecurityUser userInfo = getUserInfo();
        if (ObjectUtil.isNotNull(userInfo)) {
            // 角色标识为 TEN_SUPER_TENANT 的默认为租户管理员
            if (Constants.TEN_SUPER_USER.equals(userInfo.getUserCode()))
                result = true;
            for (Object roleCode : userInfo.getRoleCodeList()) {
                // 拥有角色标识为 TEN_SUPER_TENANT 的默认为租户管理员
                if (Constants.TEN_SUPER_ROLE.equals(roleCode)) {
                    result = true;
                    break;
                }
            }
        }
        return result;
    }

    public static Boolean isSysAdmin() {
        boolean result = false;
        SecurityUser userInfo = getUserInfo();
        if (ObjectUtil.isNotNull(userInfo)) {
            // 角色标识为 TEN_SUPER_POST 的默认为岗位管理员
            if (Constants.SYS_SUPER_USER.equals(userInfo.getUserCode())) {
                result = true;
            }
            for (Object roleCode : userInfo.getRoleCodeList()) {
                // 拥有角色标识为 TEN_SUPER_POST 的默认为岗位管理员
                if (Constants.SYS_SUPER_ROLE.equals(roleCode)) {
                    result = true;
                    break;
                }
            }
        }
        return result;
    }

    public static Boolean isAdmin() {
        boolean result = false;
        SecurityUser userInfo = getUserInfo();
        if (ObjectUtil.isNotNull(userInfo)) {
            // 角色标识为 TEN_SUPER_USER 和 SYS_SUPER_USER 的默认为管理员
            if (Constants.TEN_SUPER_USER.equals(userInfo.getUserCode()) || Constants.SYS_SUPER_USER.equals(userInfo.getUserCode())) {
                result = true;
            }
            for (Object roleCode : userInfo.getRoleCodeList()) {
                // 拥有角色标识为 TEN_SUPER_ROLE 和 SYS_SUPER_ROLE 的默认为管理员
                if (Constants.TEN_SUPER_ROLE.equals(roleCode) || Constants.SYS_SUPER_ROLE.equals(roleCode)) {
                    result = true;
                    break;
                }
            }
        }
        return result;
    }

}
